package com.hwtx.form.domain.handle.core;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.hwtx.form.annotation.FormType;
import com.hwtx.form.domain.def.FormConfigExt;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.def.FormDetailExt;
import com.hwtx.form.domain.def.meta.config.FormComponentConfig;
import com.hwtx.form.domain.ds.DataSourceContextHolder;
import com.hwtx.form.domain.handle.ColumnCallbackMapper;
import com.hwtx.form.domain.handle.FormDataHandle;
import com.hwtx.form.domain.handle.SelectItemsInfo;
import com.hwtx.form.domain.handle.datamodel.DataModel;
import com.hwtx.form.domain.handle.datamodel.DataModelRelate;
import com.hwtx.form.domain.listener.FormConfigChangeEvent;
import com.hwtx.form.domain.listener.FormDataChangeEvent;
import com.hwtx.form.domain.listener.FormDataChangeInterceptor;
import com.hwtx.form.domain.permission.DataQueryInfo;
import com.hwtx.form.domain.repo.DataModelRepo;
import com.hwtx.form.domain.repo.FormConfigRepo;
import com.hwtx.form.domain.repo.I18nRepo;
import com.hwtx.form.domain.service.ResourceService;
import com.hwtx.form.domain.service.TenantsService;
import com.hwtx.form.domain.vo.*;
import com.hwtx.form.dto.FormListQuery;
import com.hwtx.form.persistence.DataSourceRepo;
import com.hwtx.form.persistence.entity.FormDefDetail;
import com.hwtx.form.persistence.entity.SysRoleDataPermPro;
import com.hwtx.form.persistence.mapper.SysRoleDataPermProMapper;
import com.hwtx.form.query.FormValueQuery;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.JacksonUtil;
import com.hwtx.form.util.QueryParser;
import com.hwtx.form.util.QueryParserResult;
import io.geekidea.boot.auth.cache.DataPermissionCache;
import io.geekidea.boot.auth.cache.SqlParerCache;
import io.geekidea.boot.auth.util.LoginUtil;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.framework.page.Paging;
import io.geekidea.boot.system.entity.SysMenu;
import io.geekidea.boot.system.vo.DataPermissionConfig;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.event.EventListener;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.domain.FormConstants.DefaultColumn.id;
import static com.hwtx.form.persistence.DataSourceRepo.getSqlAndParam;
import static com.hwtx.form.util.FormUtil.*;

@FormType(DEFAULT_FORM_TYPE)
@Slf4j
public class DefaultFormDataHandle extends DefaultFormBaseHandle implements FormDataHandle {
    public static final List<Integer> apiInvokeComponentTypes = List.of(FormComponentType.FORM_COM_API_INVOKE.getValue(), FormComponentType.FORM_COM_CHAINED_SELECT.getValue());
    private static final Object NULL_VALUE = Collections.EMPTY_LIST;
    private final Cache<Long, QueryConfigInfo> queryConfigCache = CacheBuilder.newBuilder().expireAfterAccess(Duration.ofMinutes(10)).build();
    @Resource
    private FormConfigRepo formConfigRepo;
    @Resource
    private DataSourceRepo dataSourceRepo;
    @Resource
    private DataModelRepo dataModelRepo;
    @Resource
    private JdbcTemplate jdbcTemplate;
    @Resource
    private List<FormDataChangeInterceptor> interceptors;
    @Resource
    private DefaultFormDataReadHandle formDataReadHandle;
    @Resource
    private DefaultFormDataWriteHandle formDataWriteHandle;
    @Resource
    private I18nRepo i18nRepo;
    @Resource
    private TenantsService tenantsService;
    @Resource
    private SysRoleDataPermProMapper sysRoleDataPermProMapper;
    @Resource
    private ResourceService resourceService;
    @Resource
    private DataPermissionCache dataPermissionCache;

    private static @NotNull Map<String, Collection<Object>> getCollectionMap(Object value, String field, String[] splitValues) {
        Map<String, Collection<Object>> one = Maps.newLinkedHashMap();
        one.compute(field, (_field, params) -> {
            if (params == null) {
                params = Lists.newArrayList();
            }
            if (splitValues.length > 1) {
                String vv = splitValues[splitValues.length - 1];
            } else {
                params.add(value);
            }
            return params;
        });
        return one;
    }

    @EventListener
    public void clearQueryConfigCache(FormConfigChangeEvent formConfigChangeEvent) {
        queryConfigCache.invalidate(formConfigChangeEvent.getFormId());
    }

    @Override
    public Paging<?> page(FormDef formDef, FormListQuery formListQuery) {
        Paging<Map<String, Object>> paging = new Paging<>();
        ListRet listRet = handleList(formDef.getFormId(), formListQuery, true);
        paging.setItems(listRet.getRows());
        paging.setTotal(listRet.getCount());
        return paging;
    }

    @Override
    public FormRepData.MapFormRepData getFormData(FormDef formDef, FormValueQuery formValueQuery) {
        return formDataReadHandle.getFormData(formDef, formValueQuery);
    }

    @Override
    public Object saveFormData(FormDef formDef, FormData formData) throws Exception {
        return formDataWriteHandle.saveFormData(formDef, formData);
    }

    @Override
    public FormListVo<?> query(FormDef formDef, FormListQuery formListQuery) {
        ListRet listRet = handleList(formDef.getFormId(), formListQuery, false);
        FormListVo<Map<String, Object>> ret = new FormListVo<>();
        ret.setRows(listRet.getRows());
        ret.setCount(listRet.getCount());
        return ret;
    }

    private ListRet handleList(Long formId, FormListQuery formListQuery, boolean isPage) {
        try {
            QueryConfigInfo queryConfigInfo = getQueryConfigInfo(formId);
            checkJdbcTemplate(queryConfigInfo.getDataModel().getDs());
            List<QueryConfigInfo.RequestParam> filterExtraItems = queryConfigInfo.getFilterListExtraItems();
            Map<String, Object> whereItemAndValues = formListQuery.getSearchPostData();
            SqlBuilderElement.OrderInfo orderInfo = JacksonUtil.map2Oject(whereItemAndValues, SqlBuilderElement.OrderInfo.class);
            List<FormConfigExt.SearchConfig> searchConfigs = CollectionUtils.isNotEmpty(queryConfigInfo.getSearchConfigs()) ? queryConfigInfo.getSearchConfigs() : Lists.newArrayList();
            searchConfigs = searchConfigs.stream().filter(searchConfig -> (whereItemAndValues.get(searchConfig.getProperty()) != null && StringUtils.isNotEmpty(whereItemAndValues.get(searchConfig.getProperty()).toString())) || (whereItemAndValues.get(searchConfig.getAlias()) != null && StringUtils.isNotEmpty(whereItemAndValues.get(searchConfig.getAlias()).toString()))).toList();
            DataSourceRepo.FilterCallback callback = null;
            AtomicReference<String> tenantCodeField = new AtomicReference<>();
            if (StringUtils.isNotEmpty(LoginUtil.getLoginVo().getTenantCode())) {
                callback = table -> {
                    String field = tenantsService.getTenantTableField(table);
                    if (StringUtils.isNotEmpty(field)) {
                        tenantCodeField.set(field);
                    }
                    return field;
                };
            }
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            String url = request.getServletPath();
            List<DataQueryInfo.DataPermInfo> dataPerms = handleUrlDataPerm(formId, url);
            List<QueryConfigInfo.OrGroupParam> orGroupParams = Lists.newArrayList();
            List<DataQueryInfo.DataPermInfo> modelPerms = dataPerms.stream().map(perm -> {
                if (StringUtils.isNotEmpty(perm.getModelName())) {
                    return perm;
                }
                QueryConfigInfo.OrGroupParam orGroupParam = new QueryConfigInfo.OrGroupParam();
                orGroupParam.setGroups(perm.getInputValues());
                orGroupParams.add(orGroupParam);
                return null;
            }).filter(Objects::nonNull).toList();
            List<Pair<DataQueryInfo.DataModelParamInfo, Map<String, Object>>> dataModelParams = queryConfigInfo.getDataModelParams().stream().map(param -> {
                Map<String, Object> values = getDataModelRequestParams(param, whereItemAndValues);
                if (MapUtils.isNotEmpty(values)) {
                    return Pair.of(param, values);
                }
                return null;
            }).filter(Objects::nonNull).toList();

            Pair<Boolean, List<DataQueryInfo.DataModelFilterInfo>> dataModelFilters = handleDataModelParamInfo(modelPerms, dataModelParams);
            if (!dataModelFilters.getKey()) {
                return ListRet.builder().rows(Lists.newArrayList()).count(0).build();
            }
            String sql = dataSourceRepo.buildSelectList(queryConfigInfo.getDataModel(), queryConfigInfo.getDataModelRelates(), queryConfigInfo.getSelectItems(), filterExtraItems, searchConfigs, dataModelFilters.getValue(), orGroupParams, orderInfo, isPage, whereItemAndValues, callback);
            List<Object> param = Lists.newArrayList();
            searchConfigs.forEach(searchConfig -> {
                String key = searchConfig.getProperty();
                if (searchConfig.getAlias() != null) {
                    key = searchConfig.getAlias();
                }
                Object value = whereItemAndValues.get(key);
                if (MapUtils.isNotEmpty(searchConfig.getAddFields()) && value != null) {
                    //fixme
                    param.addAll(List.of(value.toString().split(",")));
                } else {
                    if (Objects.equals(OperatorType.LIKE.getSymbol().toLowerCase(), searchConfig.getOperator())) {
                        param.add("%" + whereItemAndValues.get(key) + "%");
                    } else {
                        param.add(value);
                    }
                }
            });
            if (queryConfigInfo.getFilterListExtraItems() != null) {
                queryConfigInfo.getFilterListExtraItems().stream().filter(item -> item.isVariable() && !Objects.equals(tenantCodeField.get(), item.getProperty())).forEach(item -> {
                    Object val = whereItemAndValues.get(item.getParamName());
                    if (val != null && StringUtils.isNotBlank(val.toString())) {
                        param.add(val);
                    }
                });
            }
            if (tenantCodeField.get() != null) {
                param.add(LoginUtil.getLoginVo().getTenantCode());
            }
            dataModelFilters.getValue().stream().filter(filter -> CollectionUtils.isNotEmpty(filter.getValues())).forEach(filter -> param.addAll(filter.getValues()));
            if (isPage) {
                int pageSize = formListQuery.getPageSize();
                int pageNum = formListQuery.getPageIndex();
                int start = (pageNum - 1) * pageSize;
                param.add(pageSize);
                param.add(start);
            }
            List<Map<String, Object>> rows = jdbcTemplate.query(sql, new ColumnCallbackMapper(ColumnCallbackMapper.RowInterceptor(), queryConfigInfo.getInvokeApiFields()), param.toArray());
            if (isPage) {
                String countSql = dataSourceRepo.buildCount(queryConfigInfo.getDataModel(), queryConfigInfo.getDataModelRelates(), filterExtraItems, searchConfigs, dataModelFilters.getValue(), orGroupParams, whereItemAndValues, callback);
                param.removeLast();
                param.removeLast();
                Long count = jdbcTemplate.queryForObject(countSql, Long.class, param.toArray());
                return ListRet.builder().rows(rows).count(count).build();
            }
            return ListRet.builder().rows(rows).count(rows.size()).build();
        } catch (Exception e) {
            log.info("DefaultFormDataHandle handleList error", e);
        } finally {
            DataSourceContextHolder.clearDataSourceKey();
        }
        return ListRet.builder().rows(Lists.newArrayList()).count(0).build();
    }

    @Override
    @Transactional
    public Object removeFormData(FormDef formDef, FormValueQuery formValueQuery) {
        List<Long> ids = formValueQuery.getValueIds();
        if (ids == null) {
            throw new RuntimeException("查询id不能为空");
        }
        try {
            QueryConfigInfo queryConfigInfo = getQueryConfigInfo(formDef.getFormId());
            checkJdbcTemplate(queryConfigInfo.getDataModel().getDs());
            Map<String, List<FormDetailExt.OperationCondition>> opMap = queryConfigInfo.getOperationConditions();
            List<FormDetailExt.OperationCondition> deleteOps = Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(opMap.get(OPERATION_DELETE))) {
                deleteOps.addAll(opMap.get(OPERATION_DELETE));
            }
            if (CollectionUtils.isNotEmpty(opMap.get(OPERATION_DELETE_BULK))) {
                deleteOps.addAll(opMap.get(OPERATION_DELETE_BULK));
            }
            interceptors.forEach(interceptor -> interceptor.before(FormDataChangeEvent.builder().type(OPERATION_DELETE).operationConditions(deleteOps).changeFormId(formDef.getFormId()).dataModel(queryConfigInfo.getDataModel()).params(formValueQuery.getParameters()).build()));

            String willDeleteSql = dataSourceRepo.buildSelectByIds(queryConfigInfo.getDataModel(), queryConfigInfo.getDataModelRelates(), id.name(), ids.size());
            List<Map<String, Object>> rows = jdbcTemplate.queryForList(willDeleteSql, ids.toArray());
            List<Pair<String, String>> deleteSql = dataSourceRepo.buildBulkDelete(queryConfigInfo.getDataModel(), queryConfigInfo.getDataModelRelates(), id.name(), ids.size());
            handleI18n(formDef, ids);
            boolean relate = CollectionUtils.isNotEmpty(queryConfigInfo.getDataModelRelates());
            if (!relate) {
                boolean ret = jdbcTemplate.update(deleteSql.getFirst().getRight(), ids.toArray()) > 0;
                if (ret) {
                    Object changeRet = sendDataDelChangeMessage(rows, formDef.getFormId(), queryConfigInfo.getDataModel());
                    if (changeRet != null) {
                        return changeRet;
                    }
                }
            }
            List<String> changeMsgs = new ArrayList<>();
            for (Pair<String, String> sql : deleteSql) {
                String table = sql.getLeft();
                if (relate) {
                    Object[] tableIds = rows.stream().map(row -> row.get(table + "_" + id.name())).toArray();
                    boolean ret = jdbcTemplate.update(sql.getRight(), tableIds) > 0;
                    if (ret) {
                        String sqlAll = dataSourceRepo.buildSelectByIds(queryConfigInfo.getDataModel(), null, id.name(), tableIds.length);
                        List<Map<String, Object>> deleteRows = jdbcTemplate.queryForList(sqlAll, tableIds);
                        Object changeRet = sendDataDelChangeMessage(deleteRows, formDef.getFormId(), queryConfigInfo.getDataModel());
                        if (changeRet != null) {
                            changeMsgs.add(changeRet.toString());
                        }
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(changeMsgs)) {
                return changeMsgs;
            }
            return true;
        } catch (ExecutionException e) {
            log.info("DefaultFormDataHandle getFormData error", e);
            return false;
        } finally {
            DataSourceContextHolder.clearDataSourceKey();
        }
    }

    private Object sendDataDelChangeMessage(List<Map<String, Object>> rows, Long formId, DataModel dataModel) {
        FormDataChangeEvent event = FormDataChangeEvent.builder().type(OPERATION_DELETE).removeItems(rows).dataModel(dataModel).changeFormId(formId).build();
        for (FormDataChangeInterceptor interceptor : interceptors) {
            Object result = interceptor.after(event);
            if (result != null) {
                return result.toString();
            }
        }
        return null;
    }

    private void handleI18n(FormDef formDef, List<Long> ids) {
        if (!formDef.getName().equalsIgnoreCase("i18n")) {
            return;
        }
        ids.forEach(id -> handleI18n(i18nRepo.getById(id)));
    }

    private QueryConfigInfo getQueryConfigInfo(Long formId) throws ExecutionException {
        return queryConfigCache.get(formId, () -> {
            QueryConfigInfo queryConfigInfo = new QueryConfigInfo();
            List<FormConfigQueryVo> formConfigQueries = formConfigRepo.listMergedFormConfig(FormConfigQuerySearchVo.builder().formId(formId).build());
            Map<String, List<SelectItemsInfo.SelectItemInfo>> selectItems = Maps.newHashMap();
            List<QueryConfigInfo.RequestParam> filterExtraItems = Lists.newArrayList();
            FormDefDetail formDefDetail = formConfigRepo.getFormDetail(formId);
            FormDetailExt formDetailExt = FormUtil.getFormDetailExt(formDefDetail.getExt());
            List<FormConfigExt.SearchConfig> searchConfigs = Lists.newArrayList();
            List<DataQueryInfo.DataModelParamInfo> dataModelParams = Lists.newArrayList();
            Map<String, String> nameAndField = Maps.newHashMap();
            String modelName = formDefDetail.getModelName();
            DataModel dataModel = dataModelRepo.getDataModel(modelName);
            if (dataModel == null) {
                throw new RuntimeException("数据模型不存在");
            }
            List<FormComponentConfig.ApiInvokeParam> invokeApiFields = Lists.newArrayList();
            Map<String, Map<String, String>> tableMapFieldAndAlias = Maps.newHashMap();
            formConfigQueries.stream().filter(vo -> StringUtils.isNotEmpty(vo.getField())).forEach(vo -> tableMapFieldAndAlias.compute(vo.getTableName(), (k, v) -> {
                if (v == null) {
                    v = Maps.newHashMap();
                }
                v.put(vo.getField(), vo.getName());
                return v;
            }));
            formConfigQueries.forEach(vo -> {
                FormConfigExt formConfigExt = getFormConfigExt(vo.getExt());
                if (vo.getListController() != null && apiInvokeComponentTypes.contains(vo.getListController())) {
                    FormComponentConfig formComponentConfig = FormComponentConfig.getFormComponentConfig(vo.getListController());
                    Map<String, Object> comExt = FormComponentConfig.getComExt(LIST, vo.getExt());
                    if (MapUtils.isNotEmpty(comExt)) {
                        FormComponentConfig.ComponentParamInfo componentParamInfo = formComponentConfig.getInvokeComponentParamInfo(comExt);
                        if (componentParamInfo != null) {
                            Map<String, Set<String>> invokeFields = Maps.newHashMap();
                            formComponentConfig.getFields(comExt).forEach((table, fields) -> {
                                if (Objects.equals(table, DEFAULT_TABLE_NAME)) {
                                    table = dataModel.getMaster();
                                }
                                Map<String, String> fieldAndAlias = tableMapFieldAndAlias.get(table);
                                fields.forEach(field -> {
                                    final String alias = fieldAndAlias.get(field);
                                    invokeFields.compute(field, (k, v) -> {
                                        if (v == null) {
                                            v = Sets.newHashSet();
                                        }
                                        if (StringUtils.isNotEmpty(alias)) {
                                            v.add(alias);
                                        }
                                        return v;
                                    });
                                });
                            });
                            invokeApiFields.add(FormComponentConfig.ApiInvokeParam.builder().componentParamInfo(componentParamInfo).formComponentConfig(formComponentConfig).fields(invokeFields).column(StringUtils.isNotBlank(vo.getName()) ? vo.getName() : vo.getField()).componentType(FormComponentType.of(vo.getListController())).build());
                        }
                    }
                }
                if (Objects.equals(vo.getIsListQuery(), (byte) 1) && StringUtils.isNotBlank(vo.getTableName())) {
                    selectItems.compute(vo.getTableName(), (k, v) -> {
                        if (v == null) {
                            v = Lists.newArrayList();
                        }
                        v.add(SelectItemsInfo.SelectItemInfo.builder().field(vo.getField()).name(vo.getName()).build());
                        return v;
                    });
                }
                if (StringUtils.isNotBlank(vo.getName())) {
                    nameAndField.put(vo.getName(), vo.getField());
                }
                if (Objects.equals(vo.getIsQueryShow(), (byte) 1)) {
                    FormConfigExt.SearchConfig searchConfig = new FormConfigExt.SearchConfig();
                    boolean dataModelQuery = false;
                    String name = vo.getName();
                    if (StringUtils.isBlank(name)) {
                        name = vo.getField();
                    }
                    if (vo.getQueryController() != null) {
                        ComponentConfigInfo componentConfigInfo = getComponentConfigInfo(vo.getQueryController(), vo, QUERY);
                        if (MapUtils.isNotEmpty(componentConfigInfo.getAddFields())) {
                            searchConfig.setAddFields(componentConfigInfo.getAddFields());
                        } else {
                            if (componentConfigInfo.getFormComponentConfig() != null) {
                                FormComponentConfig.ComponentDataModelParamInfo componentDataModelParamInfo = componentConfigInfo.getFormComponentConfig().getDataModelParamInfo(componentConfigInfo.comExt);
                                if (componentDataModelParamInfo != null) {
                                    dataModelParams.add(buildDataModelParamInfo(componentDataModelParamInfo, vo.getQueryController(), name));
                                    dataModelQuery = true;
                                }
                            }
                        }
                    }
                    if (!dataModelQuery) {
                        searchConfig.setProperty(name);
                        searchConfig.setOperator(OperatorType.EQ.getSymbol());
                        if (formConfigExt != null && formConfigExt.getQuery() != null) {
                            if (formConfigExt.getQuery().getSearch() != null) {
                                searchConfig.setOperator(formConfigExt.getQuery().getSearch().getOperator());
                            }
                        }
                        searchConfigs.add(searchConfig);
                    }
                }

                if (formConfigExt != null && formConfigExt.getList() != null) {
                    FormConfigExt.SearchConfig searchConfig = formConfigExt.getList().getSearch();
                    if (searchConfig != null) {
                        selectItems.compute(vo.getTableName(), (k, v) -> {
                            if (v == null) {
                                v = Lists.newArrayList();
                            }
                            SelectItemsInfo.SelectItemInfo selectItemsInfo = SelectItemsInfo.SelectItemInfo.builder().field(searchConfig.getProperty()).build();
                            if (!v.contains(selectItemsInfo)) {
                                v.add(selectItemsInfo);
                            }
                            return v;
                        });
                        searchConfigs.add(searchConfig);
                    }
                }
            });
            if (formDetailExt != null && formDetailExt.getListExt() != null) {
                Map<String, List<FormDetailExt.OperationCondition>> operationConditions = Maps.newHashMap();
                queryConfigInfo.operationConditions = operationConditions;
                ListUtils.emptyIfNull(formDetailExt.getListExt().getOperations()).forEach(op -> {
                    if (!Objects.equals(op.getValue(), FormDetailOperation.TARGET_PAGE.getValue()) || !Objects.equals(op.getValue(), FormDetailOperation.ACTION.getValue())) {
                        operationConditions.compute(op.getValue(), (k, v) -> {
                            if (v == null) {
                                v = Lists.newArrayList();
                            }
                            if (CollectionUtils.isNotEmpty(op.getConditions())) {
                                v.addAll(op.getConditions());
                            }
                            return v;
                        });
                    }
                });
                MapUtils.emptyIfNull(formDetailExt.getListExt().getParamMapping()).forEach((k, v) -> {
                    QueryConfigInfo.RequestParam proValueConfig = new QueryConfigInfo.RequestParam();
                    proValueConfig.setProperty(nameAndField.getOrDefault(k, k));
                    proValueConfig.setValue(v);
                    proValueConfig.setOperator(OperatorType.EQ.getSymbol());
                    proValueConfig.setParamName(k);
                    filterExtraItems.add(proValueConfig);
                });
            }
            queryConfigInfo.searchConfigs = searchConfigs;
            if (selectItems.isEmpty()) {
                throw new RuntimeException("查询字段不能为空");
            }
            List<DataModelRelate> relates = dataModelRepo.getRelates(modelName);
            queryConfigInfo.selectItems = selectItems;
            queryConfigInfo.invokeApiFields = invokeApiFields;
            queryConfigInfo.dataModel = dataModel;
            queryConfigInfo.dataModelRelates = relates;
            queryConfigInfo.dataModelParams = dataModelParams;
            queryConfigInfo.filterListExtraItems = filterExtraItems;
            return queryConfigInfo;
        });
    }

    private Pair<Boolean, List<DataQueryInfo.DataModelFilterInfo>> handleDataModelParamInfo(List<DataQueryInfo.DataPermInfo> dataPerm,
                                                                                            List<Pair<DataQueryInfo.DataModelParamInfo, Map<String, Object>>> dataModelParams) {
        List<DataQueryInfo.DataModelFilterInfo> dataModelFilters = Lists.newArrayList();
        int go = 1;
        if (CollectionUtils.isEmpty(dataPerm) && CollectionUtils.isEmpty(dataModelParams)) {
            return Pair.of(true, dataModelFilters);
        } else if (CollectionUtils.isEmpty(dataPerm) && CollectionUtils.isNotEmpty(dataModelParams)) {
            dataModelParams.forEach(pair -> {
                DataQueryInfo.DataModelParamInfo param = pair.getKey();
                buildDataModelFilter(param, dataModelFilters, List.of(convertToCollectionMap(pair.getValue())), param.getTable(), "AND");
            });
        } else if (CollectionUtils.isNotEmpty(dataPerm) && CollectionUtils.isEmpty(dataModelParams)) {
            dataPerm.stream().filter(param -> CollectionUtils.isNotEmpty(param.getInputValues()))
                    .forEach(param -> buildDataModelFilter(param, dataModelFilters, param.getInputValues(), null, "OR"));
        } else {
            Map<String, List<DataQueryInfo.DataPermInfo>> groupByModelName = dataPerm.stream().collect(Collectors.groupingBy(DataQueryInfo.DataModelInfo::getModelName));
            for (Pair<DataQueryInfo.DataModelParamInfo, Map<String, Object>> pair : dataModelParams) {
                DataQueryInfo.DataModelParamInfo param = pair.getKey();
                List<DataQueryInfo.DataPermInfo> matchedPermInfo = groupByModelName.get(param.getModelName());
                if (CollectionUtils.isNotEmpty(matchedPermInfo)) {
                    go = handleGroupDataModelInfo(param, dataModelFilters, matchedPermInfo, pair.getValue());
                    groupByModelName.remove(param.getModelName());
                } else {
                    buildDataModelFilter(param, dataModelFilters, List.of(convertToCollectionMap(pair.getValue())), param.getTable(), "AND");
                }
            }
            if (!groupByModelName.isEmpty()) {
                groupByModelName.forEach((m, params) -> params.forEach(param -> buildDataModelFilter(param, dataModelFilters, param.getInputValues(), null, "OR")));
            }
        }
        return Pair.of(go > 0, dataModelFilters);
    }

    private int handleGroupDataModelInfo(DataQueryInfo.DataModelParamInfo dataModelParamInfo, List<DataQueryInfo.DataModelFilterInfo> dataModelFilters,
                                         List<DataQueryInfo.DataPermInfo> dataPermInfos, Map<String, Object> params) {
        checkDataModelInfo(dataModelParamInfo, dataModelFilters);
        for (DataQueryInfo.DataPermInfo param : dataPermInfos) {
            if (param.getOutputs().getFirst().equals(dataModelParamInfo.getOutputs().getFirst())) {
                checkDataModelInfo(param, dataModelFilters);
                if (buildDataModelFilter(param, dataModelFilters, params, dataModelParamInfo.getTable()) < 0) {
                    return -1;
                }
            } else {
                buildDataModelFilter(param, dataModelFilters, param.getInputValues(), null, "OR");
            }
        }
        return 1;
    }

    private Map<String, Object> getDataModelRequestParams(DataQueryInfo.DataModelParamInfo param, Map<String, Object> whereItemAndValues) {
        if (param.getParser() != null) {
            Map<String, Object> splitKeyAndValues = param.getParser().parse(whereItemAndValues.get(param.getParamName()), param.getInputs());
            if (splitKeyAndValues.isEmpty()) {
                return splitKeyAndValues;
            }
            splitKeyAndValues.keySet().forEach(key -> {
                if (whereItemAndValues.containsKey(key)) {
                    throw new BusinessException("模型参数名重复，name = " + key);
                }
            });
            whereItemAndValues.putAll(splitKeyAndValues);
        }
        return whereItemAndValues;
    }

    private void buildDataModelFilter(DataQueryInfo.DataModelInfo param, List<DataQueryInfo.DataModelFilterInfo> dataModelFilters,
                                      List<Map<String, Collection<Object>>> keyAndValues, String table, String itemOp) {
        checkDataModelInfo(param, dataModelFilters);
        QueryParserResult result = SqlParerCache.get(param.getSqlModelContent(), () -> parserSql(param.getSqlModelContent()));
        Pair<String, List<Object>> sqlAndParams = getSqlAndParam(result, keyAndValues, param.getInputs().size(), itemOp);
        if (sqlAndParams != null) {
            dataModelFilters.add(DataQueryInfo.DataModelFilterInfo.builder().table(table).outputField(param.getOutputs().getFirst()).finalSql(sqlAndParams.getKey()).values(sqlAndParams.getValue()).build());
        }
    }

    private int buildDataModelFilter(DataQueryInfo.DataPermInfo dataPermInfo, List<DataQueryInfo.DataModelFilterInfo> dataModelFilters,
                                     Map<String, Object> dataModelParams, String table) {
        boolean matchRet = dataPermInfo.getInputValues().stream().anyMatch(map -> {
            for (Map.Entry<String, Collection<Object>> entry : map.entrySet()) {
                String k = entry.getKey();
                Collection<Object> v = entry.getValue();
                if (CollectionUtils.isEmpty(v)) {
                    return false;
                }
                Object value = dataModelParams.get(k);
                if (value != null) {
                    Collection<Object> collectionValue = object2Collection(value);
                    if (!CollectionUtils.containsAll(v, collectionValue)) {
                        return true;
                    }
                }
            }
            return false;
        });
        if (!matchRet) {
            return -1;
        }
        QueryParserResult result = SqlParerCache.get(dataPermInfo.getSqlModelContent(), () -> parserSql(dataPermInfo.getSqlModelContent()));
        Pair<String, List<Object>> sqlAndParams = getSqlAndParam(result, dataPermInfo.getInputValues(), dataPermInfo.getInputs().size(), "OR");
        if (sqlAndParams != null) {
            dataModelFilters.add(DataQueryInfo.DataModelFilterInfo.builder().table(table).outputField(dataPermInfo.getOutputs().getFirst()).finalSql(sqlAndParams.getKey()).values(sqlAndParams.getValue()).build());
        }
        return 1;
    }

    private DataQueryInfo.DataModelParamInfo buildDataModelParamInfo(FormComponentConfig.ComponentDataModelParamInfo componentDataModelParamInfo, Integer controller, String name) {
        DataQueryInfo.DataModelParamInfo dataModelParamInfo = new DataQueryInfo.DataModelParamInfo();
        DataModel dataModel = dataModelRepo.getDataModel(componentDataModelParamInfo.getModelName());
        if (dataModel == null) {
            throw new BusinessException("数据模型不存在, model: " + componentDataModelParamInfo.getModelName());
        }
        if (!DataModel.isSqlModel(dataModel.getModelType())) {
            throw new BusinessException("不支持非Sql模型");
        }
        dataModelParamInfo.setSqlModelContent(dataModel.getSqlContent());
        dataModelParamInfo.setInputs(componentDataModelParamInfo.getInput());
        dataModelParamInfo.setTable(componentDataModelParamInfo.getTable());
        dataModelParamInfo.setOutputs(componentDataModelParamInfo.getOutput());
        dataModelParamInfo.setParamName(name);
        dataModelParamInfo.setModelName(componentDataModelParamInfo.getModelName());
        if (Objects.equals(controller, FormComponentType.FORM_COM_CHAINED_SELECT.getValue())) {
            dataModelParamInfo.setParser(new DataQueryInfo.DefaultDataModelParamParser());
        }
        return dataModelParamInfo;
    }

    private void checkDataModelInfo(DataQueryInfo.DataModelInfo dataModelInfo, List<DataQueryInfo.DataModelFilterInfo> dataModelFilters) {
        if (CollectionUtils.isEmpty(dataModelInfo.getOutputs()) || dataModelInfo.getOutputs().size() > 1) {
            throw new BusinessException("不支持的参数个数，参数必须且只能有1个查询结果");
        }
        if (dataModelFilters.stream().anyMatch(filter -> filter.getOutputField().equals(dataModelInfo.getOutputs().getFirst()))) {
            throw new BusinessException("模型参数名重复，name = " + dataModelInfo.getOutputs().getFirst().getKey());
        }
    }

    private QueryParserResult parserSql(String sql) {
        try {
            return QueryParser.parser(sql);
        } catch (JSQLParserException e) {
            throw new BusinessException("数据模型SQL有误, sql: " + sql);
        }
    }

    private List<DataQueryInfo.DataPermInfo> handleUrlDataPerm(Long formId, String requestUrl) {
        Map<String, Long> functionPerms = LoginUtil.getLoginVo().getFormFunPerms().get(formId);
        if (functionPerms == null || !functionPerms.containsKey(requestUrl)) {
            return Lists.newArrayList();
        }
        Long resourceId = functionPerms.get(requestUrl);
        List<DataQueryInfo.DataPermInfo> ret = dataPermissionCache.get(resourceId, () -> {
            List<DataQueryInfo.DataPermInfo> infos = Lists.newArrayList();
            List<SysRoleDataPermPro> permPros = sysRoleDataPermProMapper.selectList(new LambdaQueryWrapper<SysRoleDataPermPro>().eq(SysRoleDataPermPro::getMenuId, resourceId).in(SysRoleDataPermPro::getRoleId, LoginUtil.getLoginVo().getRoleIds()));
            if (CollectionUtils.isEmpty(permPros)) {
                return infos;
            }
            for (SysRoleDataPermPro sysRoleDataPermPro : permPros) {
                JacksonUtil.json2Map(sysRoleDataPermPro.getDataPermConfig()).forEach((field, value) -> {
                    DataQueryInfo.DataPermInfo info = getDataPermValues(value, field, sysRoleDataPermPro.getMenuId());
                    if (StringUtils.isNotEmpty(info.getModelName())) {
                        DataModel dataModel = dataModelRepo.getDataModel(info.getModelName());
                        info.setInputs(extractSqlVariables(dataModel.getSqlContent()).stream().map(var -> {
                            FormComponentConfig.ParamMapping paramMapping = new FormComponentConfig.ParamMapping();
                            paramMapping.setKey(var);
                            return paramMapping;
                        }).toList());
                        info.setSqlModelContent(dataModel.getSqlContent());
                    }
                    infos.add(info);
                });
            }
            return infos;
        });
        if (ret.isEmpty()) {
            dataPermissionCache.set(resourceId, NULL_VALUE);
            return ret;
        } else {
            dataPermissionCache.set(resourceId, ret);
        }
        return ret;
    }

    private DataQueryInfo.DataPermInfo getDataPermValues(Object value, String field, Long resourceId) {

        if (value == null) {
            return null;
        }
        DataQueryInfo.DataPermInfo info = new DataQueryInfo.DataPermInfo();
        if (value instanceof Map<?, ?>) {
            SysRoleDataPermPro.ResourceDataPermConfig resourceDataPermConfig = JacksonUtil.map2ObjectWithAnno((Map<?, ?>) value, SysRoleDataPermPro.ResourceDataPermConfig.class);
            info.setModelName(resourceDataPermConfig.getModelName());
            value = resourceDataPermConfig.getValue();
            SysMenu sysMenu = resourceService.getById(resourceId);
            List<DataPermissionConfig.RDataPermissionConfig> dataPermConfig = JacksonUtil.json2Array(sysMenu.getDataPermConfig(), new TypeReference<>() {
            });
            if (StringUtils.isNotEmpty(resourceDataPermConfig.getModelName())) {
                dataPermConfig.stream().filter(config -> Objects.equals(config.getModelName(), resourceDataPermConfig.getModelName()))
                        .findFirst().ifPresent(config -> info.setOutputs(config.getOutput()));
            }
        }
        List<Map<String, Collection<Object>>> values = Lists.newArrayList();
        if (value instanceof Collection<?>) {
            List<Map<String, Object>> filters = (List<Map<String, Object>>) value;
            filters.forEach(item -> values.add(convertToCollectionMap(item)));
        } else if (value instanceof String) {
            String[] fields = field.split("_");
            String[] splitValues = value.toString().split(",");
            if (fields.length == 1) {
                Map<String, Collection<Object>> one = getCollectionMap(value, fields[0], splitValues);
                values.add(one);
            } else {
                if (fields.length > splitValues.length) {
                    log.error("数据模型权限配置有误，参数数量大于值数量, field: {}, value: {}", field, value);
                    throw new BusinessException("数据模型权限配置有误, field: " + field + ", value: " + value);
                }
                for (int i = 0; i < fields.length; i++) {
                    Map<String, Collection<Object>> fieldAndValue = Maps.newLinkedHashMap();
                    fieldAndValue.put(fields[i], Arrays.asList(splitValues[i].split(":")));
                    values.add(fieldAndValue);
                }
            }
        }
        info.setInputValues(values);
        return info;
    }

    private Collection<Object> object2Collection(Object item) {
        if (item instanceof Collection<?>) {
            return (Collection<Object>) item;
        } else if (item instanceof String) {
            if (item.toString().contains(",")) {
                return Arrays.asList(item.toString().split(","));
            }
        }
        return Lists.newArrayList(item);
    }

    @Getter
    @Builder
    static class ListRet {
        List<Map<String, Object>> rows;
        long count;
    }
}
